package com.sugar.subscribe.course.module.business.service.coach;

import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.sugar.subscribe.course.framework.mybatis.core.query.MPJLambdaWrapperX;
import com.sugar.subscribe.course.module.business.controller.admin.serviceclass.vo.ServiceIdsVO;
import com.sugar.subscribe.course.module.business.controller.admin.venue.vo.VenueSimpleRespVO;
import com.sugar.subscribe.course.module.business.controller.app.coach.vo.*;
import com.sugar.subscribe.course.module.business.controller.app.serviceclass.vo.AppServiceClassRespVO;
import com.sugar.subscribe.course.module.business.dal.dataobject.coachcertificate.CoachCertificateDO;
import com.sugar.subscribe.course.module.business.dal.dataobject.coachcomment.CoachCommentDO;
import com.sugar.subscribe.course.module.business.dal.dataobject.coachplan.CoachPlanDO;
import com.sugar.subscribe.course.module.business.dal.dataobject.coachservice.CoachServiceDO;
import com.sugar.subscribe.course.module.business.dal.dataobject.coachvenue.CoachVenueDO;
import com.sugar.subscribe.course.module.business.dal.dataobject.coachwallet.CoachWalletDO;
import com.sugar.subscribe.course.module.business.dal.dataobject.serviceclass.ServiceClassDO;
import com.sugar.subscribe.course.module.business.dal.dataobject.user.BizUserDO;
import com.sugar.subscribe.course.module.business.dal.dataobject.venue.VenueDO;
import com.sugar.subscribe.course.module.business.dal.mysql.coachcertificate.CoachCertificateMapper;
import com.sugar.subscribe.course.module.business.dal.mysql.coachcomment.CoachCommentMapper;
import com.sugar.subscribe.course.module.business.dal.mysql.coachplan.CoachPlanMapper;
import com.sugar.subscribe.course.module.business.dal.mysql.coachservice.CoachServiceMapper;
import com.sugar.subscribe.course.module.business.dal.mysql.coachvenue.CoachVenueMapper;
import com.sugar.subscribe.course.module.business.dal.mysql.coachwallet.CoachWalletMapper;
import com.sugar.subscribe.course.module.business.dal.mysql.serviceclass.ServiceClassMapper;
import com.sugar.subscribe.course.module.business.dal.mysql.venue.VenueMapper;
import com.sugar.subscribe.course.module.business.dal.redis.objlock.ObjectLockRedisDAO;
import com.sugar.subscribe.course.module.business.service.user.BizUserService;
import com.sugar.subscribe.course.module.system.api.auth.AuthApi;
import com.sugar.subscribe.course.module.system.api.auth.dto.AppAuthDTO;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;

import java.util.*;
import java.util.stream.Collectors;

import com.sugar.subscribe.course.module.business.controller.admin.coach.vo.*;
import com.sugar.subscribe.course.module.business.dal.dataobject.coach.CoachDO;
import com.sugar.subscribe.course.framework.common.pojo.PageResult;
import com.sugar.subscribe.course.framework.common.util.object.BeanUtils;

import com.sugar.subscribe.course.module.business.dal.mysql.coach.CoachMapper;

import static com.sugar.subscribe.course.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.sugar.subscribe.course.module.business.enums.ErrorCodeConstants.*;

/**
 * 教练信息 Service 实现类
 *
 * @author Ysugar
 */
@Service
@Validated
public class CoachServiceImpl implements CoachService {

    @Resource
    private CoachMapper coachMapper;
    @Resource
    private CoachServiceMapper coachServiceMapper;
    @Resource
    private CoachVenueMapper coachVenueMapper;
    @Resource
    private CoachWalletMapper coachWalletMapper;

    @Resource
    private CoachCertificateMapper coachCertificateMapper;
    @Resource
    private CoachCommentMapper coachCommentMapper;
    @Resource
    private CoachPlanMapper coachPlanMapper;

    @Resource
    private VenueMapper venueMapper;
    @Resource
    private ServiceClassMapper serviceClassMapper;

    @Resource
    private AuthApi authApi;

    @Resource
    private BizUserService userService;

    @Resource
    private ObjectLockRedisDAO redisLockDAO;

    @Override
    public Long createCoach(CoachSaveReqVO createReqVO) {
        // 插入
        CoachDO coach = BeanUtils.toBean(createReqVO, CoachDO.class);
        coachMapper.insert(coach);

        // 返回
        return coach.getId();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateCoach(CoachSaveReqVO updateReqVO) {
        // 校验存在
        getCoachValidate(updateReqVO.getId());
        // 更新
        CoachDO updateObj = BeanUtils.toBean(updateReqVO, CoachDO.class);
        coachMapper.updateById(updateObj);

        // 更新服务类目
        updateServiceClass(updateObj.getId(), updateReqVO.getServiceIds());

        // 更新场地
        updateVenue(updateObj.getId(), updateReqVO.getVenueIds());
    }

    @Override
    public void deleteCoach(Long id) {
        // 校验存在
        getCoachValidate(id);
        // 删除
        coachMapper.deleteById(id);
    }

    @Override
        public void deleteCoachListByIds(List<Long> ids) {
        // 删除
        coachMapper.deleteByIds(ids);
        }


    public CoachDO getCoachValidate(Long id) {
        CoachDO coach = coachMapper.selectById(id);
        if (coach == null) {
            throw exception(COACH_NOT_EXISTS);
        }
        return coach;
    }

    @Override
    public CoachDO getCoach(Long id) {
        return coachMapper.selectById(id);
    }

    @Override
    public CoachDetailRespVO getCoachDetail(Long coachId) {
        CoachDO coach = getCoachValidate(coachId);
        CoachDetailRespVO respVO = BeanUtils.toBean(coach, CoachDetailRespVO.class);

        // 获取教练场地信息
        List<CoachVenueDO> venues = coachVenueMapper.selectList(CoachVenueDO::getCoachId, coachId);
        respVO.setVenueIds(venues.stream().map(CoachVenueDO::getVenueId).collect(Collectors.toList()));

        // 获取教练服务类目
        List<CoachServiceDO> services = coachServiceMapper.selectList(CoachServiceDO::getCoachId, coachId);
        respVO.setServiceIds(services.stream().map(o -> new Long[]{o.getServicePId(), o.getServiceId()}).collect(Collectors.toList()));

        // 获取教练资金信息
        CoachWalletDO coachWallet = coachWalletMapper.selectOne(CoachWalletDO::getCoachId, coachId);
        respVO.setBalance(coachWallet.getBalance()).setTotalEarnings(coachWallet.getTotalEarnings());

        return respVO;
    }

    @Override
    public PageResult<CoachDO> getCoachPage(CoachPageReqVO pageReqVO) {
        return coachMapper.selectPage(pageReqVO);
    }

    @Override
    public void auditCoach(CoachAuditReqVO auditReqVO) {
        Integer auditStatus = auditReqVO.getAuditStatus();
        if (auditStatus.equals(10))
            auditReqVO.setRefuseCause("");

        coachMapper.update(CoachDO::getId, auditReqVO.getId(),
                CoachDO::getAuditStatus, auditStatus, CoachDO::getRefuseCause, auditReqVO.getRefuseCause());
    }

    @Override
    public void updateCoachAmount(CoachAmountUpdateReqVO amountUpdateReqVO) {
        // 更新资金信息
        coachWalletMapper.update(CoachWalletDO::getCoachId, amountUpdateReqVO.getId(),
                CoachWalletDO::getBalance, amountUpdateReqVO.getBalance(), CoachWalletDO::getTotalEarnings, amountUpdateReqVO.getTotalEarnings());
    }

    @Override
    public List<AppServiceClassRespVO> getCoachServiceClassListByCoachId(Long coachId) {
        List<AppServiceClassRespVO> respVOList = coachServiceMapper.getCoachServiceClassByCoachId(coachId);

        // 获取父级
        List<ServiceClassDO> parentServiceClassList = serviceClassMapper.selectByIds(respVOList.stream().map(AppServiceClassRespVO::getParentId).distinct().collect(Collectors.toList()));
        respVOList.addAll(BeanUtils.toBean(parentServiceClassList, AppServiceClassRespVO.class));

        return respVOList;
    }

    @Override
    public List<AppServiceClassRespVO> getCoachServiceClassTreeByCoachId(Long coachId) {
        List<AppServiceClassRespVO> childrenList = coachServiceMapper.getCoachServiceClassByCoachId(coachId);
        
        // 根据父级ID分组
        Map<Long, List<AppServiceClassRespVO>> serviceClassMap = childrenList.stream().collect(Collectors.groupingBy(AppServiceClassRespVO::getParentId));

        // 获取父级
        List<ServiceClassDO> parentServiceClassList = serviceClassMapper.selectByIds(serviceClassMap.keySet());
        List<AppServiceClassRespVO> respVOList = BeanUtils.toBean(parentServiceClassList, AppServiceClassRespVO.class);

        for (AppServiceClassRespVO respVO : respVOList) {
            respVO.setChildren(serviceClassMap.get(respVO.getId()));
        }

        return respVOList;
    }

    @Override
    public List<CoachSimpleRespVO> getAllCoach() {
        return BeanUtils.toBean(coachMapper.selectList(Wrappers.<CoachDO>lambdaQuery()
                .eq(CoachDO::getAuditStatus, 10)
                .eq(CoachDO::getIsWork, 1)
                .eq(CoachDO::getStatus, 1)), CoachSimpleRespVO.class);
    }

    // ======================================== 其它 接口 ========================================


    @Override
    public void updateCoachReceivedOrderDuration(Long coachId) {
        Long duration = coachMapper.getCoachAvgReceivedOrderDuration(coachId);
        if (duration != null)
            coachMapper.update(CoachDO::getId, coachId, CoachDO::getAvgAcceptTime, duration);
    }

    // ======================================== APP 接口 ========================================

    @Override
    @Transactional(rollbackFor = Exception.class)
    public AppAuthDTO coachApply(AppBizUserCoachApplyReqVO coachApplyReqVO, Long userId) {
        // 获取用户信息
        BizUserDO user = userService.getBizUserValidate(userId);

        CoachDO coach;
        // 判断教练是否已入驻
        if (user.getIsCoach()){
            // 获取教练信息
            coach = coachMapper.selectById(user.getCoachUserId());
        }else {
            coach = redisLockDAO.lockGet(userId, 60000L, () -> {
                // 获取一下教练信息是否已经存在
                CoachDO coachDO = coachMapper.selectOne(CoachDO::getUserId, userId);
                if (null == coachDO){
                    // 赋值基本信息
                    coachDO = BeanUtils.toBean(coachApplyReqVO, CoachDO.class);
                    coachDO.setUserId(userId).setAuditStatus(0).setStatus(true);

                    // 插入教练信息
                    coachMapper.insert(coachDO);

                    Long coachId = coachDO.getId();

                    // 更新用户信息里的教练信息
                    userService.updateUserCoachInfo(userId, coachId);

                    // 插入教练其它信息
                    // 插入教练场地
                    updateVenue(coachId, coachApplyReqVO.getVenueIds());

                    // 插入服务类目
                    List<Long[]> serviceIdList = coachApplyReqVO.getServiceIds().stream().distinct().map(id -> {
                        ServiceClassDO serviceClassDO = serviceClassMapper.selectById(id);
                        if (serviceClassDO == null)
                            throw exception(SERVICE_CLASS_NOT_EXISTS);
                        if (serviceClassDO.getParentId() == 0)
                            throw exception(SERVICE_CLASS_NOT_SELECT_FIRST_CLASS);
                        return new Long[]{serviceClassDO.getParentId(), id};
                    }).collect(Collectors.toList());
                    updateServiceClass(coachId, serviceIdList);

                    // 插入教练钱包信息
                    coachWalletMapper.insert(CoachWalletDO.builder().coachId(coachId).build());
                }

                return coachDO;
            });
        }

        // 判断是否被禁用
        Boolean status = coach.getStatus();
        if (!status)
            throw exception(COACH_DISABLED);

        AppAuthDTO appAuthDTO = authApi.appLogin(coach.getId(), coach.getName(), true);

        // 获取教练信息详情
        appAuthDTO.setUserInfo(appGetCoach(coach.getId()));

        return appAuthDTO;
    }

    @Override
    public AppAuthDTO login(Long userId) {
        BizUserDO user = userService.getBizUserValidate(userId);

        if (Boolean.FALSE.equals(user.getIsCoach()))
            throw exception(COACH_APPLY_NOT_EXISTS);

        // 获取教练信息并登录
        CoachDO coach = getCoachValidate(user.getCoachUserId());

        // 判断是否被禁用
        Boolean status = coach.getStatus();
        if (!status)
            throw exception(COACH_DISABLED);

        AppAuthDTO appAuthDTO = authApi.appLogin(coach.getId(), coach.getName(), true);

        // 获取教练信息详情
        appAuthDTO.setUserInfo(appGetCoach(coach.getId()));

        return appAuthDTO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void appUpdateCoach(AppBizUserCoachApplyReqVO updateReqVO, Long coachId) {
        CoachDO oldCoach = getCoachValidate(coachId);

        // 更新基本信息
        CoachDO coach = BeanUtils.toBean(updateReqVO, CoachDO.class);
        coach.setId(coachId).setAuditStatus(0)
                // 设置佣金，防止用户修改后置空
                .setCommissionRate(oldCoach.getCommissionRate());
        coachMapper.updateById(coach);

        // 删除旧的场地
        updateVenue(coachId, updateReqVO.getVenueIds());

        // 删除旧服务类目
        List<Long[]> serviceIdList = updateReqVO.getServiceIds().stream().distinct().map(id -> {
            ServiceClassDO serviceClassDO = serviceClassMapper.selectById(id);
            if (serviceClassDO == null)
                throw exception(SERVICE_CLASS_NOT_EXISTS);
            if (serviceClassDO.getParentId() == 0)
                throw exception(SERVICE_CLASS_NOT_SELECT_FIRST_CLASS);
            return new Long[]{serviceClassDO.getParentId(), id};
        }).collect(Collectors.toList());

        updateServiceClass(coachId, serviceIdList);
    }

    @Override
    public void updateCoachWork(Boolean isWork, Long coachId) {
        coachMapper.update(CoachDO::getId, coachId, CoachDO::getIsWork, isWork);
    }

    @Override
    public AppBizCoachRespVO appGetCoach(Long coachId) {
        CoachDO coach = getCoachValidate(coachId);
        AppBizCoachRespVO respVO = BeanUtils.toBean(coach, AppBizCoachRespVO.class);

        // 获取场地信息
        List<CoachVenueDO> coachList = coachVenueMapper.selectList(CoachVenueDO::getCoachId, coachId);
        respVO.setVenueIds(coachList.stream().map(CoachVenueDO::getVenueId).collect(Collectors.toList()));

        // 获取授课类目
        List<CoachServiceDO> serviceIds = coachServiceMapper.selectList(CoachServiceDO::getCoachId, coachId);
        respVO.setServiceIds(serviceIds.stream().map(o -> BeanUtils.toBean(o, ServiceIdsVO.class)).collect(Collectors.toList()));

        return respVO;
    }

    @Override
    public PageResult<AppBizCoachHomePageRespVO> getCoachHomePage(AppBizCoachHomePageReqVO reqVO) {
        PageResult<AppBizCoachHomePageRespVO> pageResult = coachMapper.appSelectCoachHomePage(reqVO);

        for (AppBizCoachHomePageRespVO respVO : pageResult.getList()) {
            // 获取证书
            respVO.setCertificates(
                    coachCertificateMapper.selectList(CoachCertificateDO::getCoachId, respVO.getId(), CoachCertificateDO::getAuditStatus, 10)
                            .stream()
                            .map(CoachCertificateDO::getName)
                            .collect(Collectors.toList()))
                    // 获取最近的三条评论人头像
                    .setCommentAvatarUsers(coachCommentMapper.selectTopThreeUserCommentUserAvatar(respVO.getId()));
        }

        return pageResult;
    }

    @Override
    public AppBizCoachDetailRespVO appGetCoachDetail(Long coachId) {
        AppBizCoachDetailRespVO respVO = coachMapper.appGetCoachDetail(coachId);

        if (respVO == null)
            throw exception(COACH_NOT_EXISTS);

        // 获取证书
        respVO.setCertificates(
                        coachCertificateMapper.selectList(CoachCertificateDO::getCoachId, respVO.getId(), CoachCertificateDO::getAuditStatus, 10)
                                .stream()
                                .map(CoachCertificateDO::getName)
                                .collect(Collectors.toList()))
                // 获取最近的三条评论人头像
                .setCommentAvatarUsers(coachCommentMapper.selectTopThreeUserCommentUserAvatar(respVO.getId()))
                // 获取可约区域
                .setVenues(coachVenueMapper.selectList(CoachVenueDO::getCoachId, respVO.getId())
                        .stream()
                        .map(CoachVenueDO::getVenueName)
                        .collect(Collectors.toList()))
                // 获取课程方案
                .setPlans(coachPlanMapper.selectJoinList(AppBizCoachDetailRespVO.PlanRespVO.class, new MPJLambdaWrapperX<CoachPlanDO>()
                        .selectAll(CoachPlanDO.class)
                        .selectAs(ServiceClassDO::getName, AppBizCoachDetailRespVO.PlanRespVO::getServiceName)
                        .selectAs("t.price / 100", AppBizCoachDetailRespVO.PlanRespVO::getAmount)
                        .leftJoin(ServiceClassDO.class, ServiceClassDO::getId, CoachPlanDO::getServiceId)
                        .eq(CoachPlanDO::getCoachId, coachId)
                        .eq(CoachPlanDO::getAuditStatus, 10)
                        .orderByDesc(CoachPlanDO::getSort)));

        return respVO;
    }

    @Override
    public List<VenueSimpleRespVO> getCoachVenueListByCoachId(Long coachId, Long cityId) {
        return BeanUtils.toBean(coachVenueMapper.selectList(Wrappers.<CoachVenueDO>lambdaQuery()
                .eq(CoachVenueDO::getCoachId, coachId)
                .eq(cityId != null, CoachVenueDO::getCityId, cityId)), VenueSimpleRespVO.class,
                p -> p.setName(p.getVenueName()).setId(p.getVenueId()));
    }

    private void updateServiceClass(Long coachId, List<Long[]> serviceIdList) {
        serviceIdList = serviceIdList.stream().filter(ids -> ids.length == 2).collect(Collectors.toList());

        List<Long[]> toInsertList;

        // 获取数据库当前数据
        List<CoachServiceDO> oldServiceCLassList = coachServiceMapper.selectList(CoachServiceDO::getCoachId, coachId);
        if (CollUtil.isNotEmpty(oldServiceCLassList)){
            Map<Long, Long> oldServiceCLassMap = oldServiceCLassList.stream()
                    .collect(Collectors.toMap(CoachServiceDO::getServiceId, CoachServiceDO::getId));

            toInsertList = new ArrayList<>();

            for (Long[] ids : serviceIdList) {
                Long removeCoachServiceClassId = oldServiceCLassMap.remove(ids[1]);
                if (removeCoachServiceClassId == null) {
                    // 如果现有数据没有该记录，说明是插入数据
                    toInsertList.add(ids);
                }
            }

            // 剩余的 oldServiceCLassMap 中的就是需要删除的记录
            coachServiceMapper.deleteByIds(oldServiceCLassMap.values());
        }else
            toInsertList = serviceIdList;

        // 插入新服务类目
        toInsertList.forEach(serviceIds ->{
            if (serviceIds.length == 2){
                coachServiceMapper.insert(
                        CoachServiceDO
                                .builder()
                                .coachId(coachId)
                                .servicePId(serviceIds[0])
                                .serviceId(serviceIds[1])
                                .build()
                );
            }
        });
    }

    private void updateVenue(Long coachId, List<Long> venueIds) {
        venueIds = venueIds.stream().distinct().collect(Collectors.toList());

        List<Long> toInsertList;

        // 获取数据库当前数据
        List<CoachVenueDO> oldVenues = coachVenueMapper.selectList(CoachVenueDO::getCoachId, coachId);
        if (CollUtil.isNotEmpty(oldVenues)){
            Map<Long, Long> oldVenuesMap = oldVenues.stream()
                    .collect(Collectors.toMap(CoachVenueDO::getVenueId, CoachVenueDO::getId));

            toInsertList = new ArrayList<>();

            for (Long venueId : venueIds) {
                Long removeVenueId = oldVenuesMap.remove(venueId);
                if (removeVenueId == null) {
                    // 如果现有数据没有该记录，说明是插入数据
                    toInsertList.add(venueId);
                }
            }

            // 剩余的 oldVenuesMap 中的就是需要删除的记录
            coachVenueMapper.deleteByIds(oldVenuesMap.values());
        }else
            toInsertList = venueIds;

        // 插入新教练场地
        for (Long venueId : toInsertList) {
            VenueDO venue = venueMapper.selectById(venueId);
            coachVenueMapper.insert(CoachVenueDO.builder()
                    .coachId(coachId)
                    .venueId(venue.getId())
                    .venueName(venue.getName())
                    .cityId(venue.getCityId())
                    .cityName(venue.getCityName())
                    .build());
        }
    }
}